<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Web Bluetooth Demo - Minimal</title>
  <style>
    body{font-family: Arial; padding: 16px;}
    button{margin: 6px;}
    #devices {margin-top: 12px;}
    .dev {padding:8px; border:1px solid #ddd; margin-bottom:8px;}
  </style>
</head>
<body>
  <h2>Web Bluetooth 最小 Demo</h2>
  <p>
    说明：在 Chrome/Edge 上测试，必须在 HTTPS 或 localhost 下运行。iOS 支持有限。
  </p>
  <div>
    <button id="scanBtn">Scan / 选择设备</button>
    <button id="disconnectBtn" disabled>Disconnect</button>
    <button id="sendBtn" disabled>Send demo bytes</button>
  </div>

  <div id="status">状态：空</div>
  <div id="devices"></div>

<script>
  let device = null;
  let server = null;
  let writableChar = null;

  const log = (s) => {
    document.getElementById('status').textContent = '状态：' + s;
    console.log(s);
  };

  // 点击扫描/选择设备
  document.getElementById('scanBtn').addEventListener('click', async () => {
    try {
      log('请求设备（会打开浏览器设备选择对话框）...');
      // 如果你知道服务 UUID，最好把它放到 optionalServices 中，例如:
      // optionalServices: ['00001234-0000-1000-8000-00805f9b34fb']
      device = await navigator.bluetooth.requestDevice({
        acceptAllDevices: true,
        optionalServices: [] // 可在此添加要访问的 service UUID 列表
      });

      device.addEventListener('gattserverdisconnected', onDisconnected);
      log('已选择设备：' + (device.name || device.id) + '，正在连接...');
      server = await device.gatt.connect();

      log('已连接，发现服务与特征...');
      // 尝试获取所有 primary services（某些浏览器需要在 requestDevice 中先声明 optionalServices）
      const services = await server.getPrimaryServices();
      // 查找第一个具有 write 或 writeWithoutResponse 属性的特征
      for (const svc of services) {
        const characteristics = await svc.getCharacteristics();
        for (const c of characteristics) {
          const props = c.properties;
          if (props.write || props.writeWithoutResponse) {
            writableChar = c;
            log('找到可写特征: service=' + svc.uuid + ' characteristic=' + c.uuid);
            break;
          }
        }
        if (writableChar) break;
      }

      if (!writableChar) {
        log('未找到可写特征，请确认 optionalServices 是否包含目标 service UUID。');
      } else {
        document.getElementById('sendBtn').disabled = false;
      }
      document.getElementById('disconnectBtn').disabled = false;
    } catch (err) {
      console.error(err);
      log('错误: ' + err);
    }
  });

  // 断开处理
  async function onDisconnected() {
    log('设备已断开');
    device = null;
    server = null;
    writableChar = null;
    document.getElementById('sendBtn').disabled = true;
    document.getElementById('disconnectBtn').disabled = true;
  }

  document.getElementById('disconnectBtn').addEventListener('click', async () => {
    if (device && device.gatt.connected) {
      log('正在断开...');
      device.gatt.disconnect();
      onDisconnected();
    }
  });

  // 发送 demo bytes（分块发送）
  document.getElementById('sendBtn').addEventListener('click', async () => {
    if (!writableChar) {
      log('没有可写特征');
      return;
    }
    // 生成示例数据（1KB）
    const total = 1024;
    const bytes = new Uint8Array(total);
    for (let i = 0; i < total; i++) bytes[i] = i % 256;

    // Web Bluetooth 没有标准方式直接查询 MTU，使用保守分块，比如 180 或 20
    const chunkSize = 180;
    let offset = 0, part = 0;
    try {
      while (offset < bytes.length) {
        const end = Math.min(offset + chunkSize, bytes.length);
        const chunk = bytes.slice(offset, end);
        // writeValue 在 Web Bluetooth 中用于写入（行为由底层决定是否响应）
        await writableChar.writeValue(chunk);
        offset = end;
        part++;
        // 小延时，避免压垮设备（必要时增大）
        await new Promise(r => setTimeout(r, 20));
      }
      log('发送完成，分片数: ' + part + ', 字节数: ' + bytes.length);
    } catch (err) {
      console.error(err);
      log('发送失败: ' + err);
    }
  });
</script>
</body>
</html>
